﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace theRift
{
    public class RayHelper
    {
        public static bool Intersects(Ray ray, Vector3 a, Vector3 b, Vector3 c, Vector3 normal, bool positiveSide, bool negativeSide, out float t)
        {
            t = 0;
            {
                float denom = Vector3.Dot(normal, ray.Direction);

                if (denom > float.Epsilon)
                {
                    if (!negativeSide)
                        return false;
                }
                else if (denom < -float.Epsilon)
                {
                    if (!positiveSide)
                        return false;
                }
                else
                {
                    return false;
                }

                t = Vector3.Dot(normal, a - ray.Position) / denom;

                if (t < 0)
                {
                    // Interersection is behind origin
                    return false;
                }
            }

            // Calculate the largest area projection plane in X, Y or Z.
            int i0, i1;
            {
                float n0 = Math.Abs(normal.X);
                float n1 = Math.Abs(normal.Y);
                float n2 = Math.Abs(normal.Z);

                i0 = 1;
                i1 = 2;

                if (n1 > n2)
                {
                    if (n1 > n0) i0 = 0;
                }
                else
                {
                    if (n2 > n0) i1 = 0;
                }
            }

            float[] A = { a.X, a.Y, a.Z };
            float[] B = { b.X, b.Y, b.Z };
            float[] C = { c.X, c.Y, c.Z };
            float[] R = { ray.Direction.X, ray.Direction.Y, ray.Direction.Z };
            float[] RO = { ray.Position.X, ray.Position.Y, ray.Position.Z };

            // Check the intersection point is inside the triangle.
            {
                float u1 = B[i0] - A[i0];
                float v1 = B[i1] - A[i1];
                float u2 = C[i0] - A[i0];
                float v2 = C[i1] - A[i1];
                float u0 = t * R[i0] + RO[i0] - A[i0];
                float v0 = t * R[i1] + RO[i1] - A[i1];

                float alpha = u0 * v2 - u2 * v0;
                float beta = u1 * v0 - u0 * v1;
                float area = u1 * v2 - u2 * v1;

                float EPSILON = 1e-3f;

                float tolerance = EPSILON * area;

                if (area > 0)
                {
                    if (alpha < tolerance || beta < tolerance || alpha + beta > area - tolerance)
                        return false;
                }
                else
                {
                    if (alpha > tolerance || beta > tolerance || alpha + beta < area - tolerance)
                        return false;
                }
            }

            return true;
        }

        public static Nullable<float> RayPlaneIntersects(Ray ray, Plane plane)
        {
            float denom = Vector3.Dot(plane.Normal, ray.Direction);
            if (Math.Abs(denom) < float.Epsilon)
            {
                return null;
            }
            else
            {
                float nom = Vector3.Dot(plane.Normal, ray.Position) + plane.D;
                float t = -(nom / denom);

                if (t >= 0)
                    return t;

                return null;
            }
        }

        public static void GetPointerRay(Vector2 pointerPos, CameraManager cameraManager, out Ray pointerRay)
        {
            double screenSpaceX = (pointerPos.X / (Settings.PreferredBackBufferWidth / 2) - 1.0f) * cameraManager.ActiveCamera.AspectRatio;
            double screenSpaceY = (1.0f - pointerPos.Y / (Settings.PreferredBackBufferHeight / 2));

            double viewRatio = Math.Tan(cameraManager.ActiveCamera.FOV / 2);

            screenSpaceX = screenSpaceX * viewRatio;
            screenSpaceY = screenSpaceY * viewRatio;

            Vector3 near3DWorldPoint = new Vector3(
                (float)(screenSpaceX * cameraManager.ActiveCamera.NearPlane),
                (float)(screenSpaceY * cameraManager.ActiveCamera.NearPlane),
                (float)(-cameraManager.ActiveCamera.NearPlane));

            Vector3 far3DWorldPoint = new Vector3(
                (float)(screenSpaceX * cameraManager.ActiveCamera.FarPlane),
                (float)(screenSpaceY * cameraManager.ActiveCamera.FarPlane),
                (float)(-cameraManager.ActiveCamera.FarPlane));

            Matrix invView = Matrix.Invert(cameraManager.ActiveCamera.View);
            Vector3 worldSpaceNear = Vector3.Transform(near3DWorldPoint, invView);
            Vector3 worldSpaceFar = Vector3.Transform(far3DWorldPoint, invView);

            pointerRay = new Ray(worldSpaceNear, worldSpaceFar - worldSpaceNear);
            pointerRay = new Ray(pointerRay.Position, Vector3.Normalize(pointerRay.Direction));
        }
    }
}
